/*日志落地模块的实现
    1. 抽象落地基类
    2. 派生子类（根据不同的方向进行派生
    3. 使用工厂模式进行创建与表示的分离
*/
#ifndef __M__SINK_H__
#define __M__SINK_H__
#include "util.hpp"
#include <memory>
#include <fstream>
#include <sstream>
#include <cassert>

namespace log
{
    class LogSink
    {
    public:
        using ptr = std::shared_ptr<LogSink>;
        LogSink()
        {
        }
        virtual ~LogSink() {}
        virtual void log(const char *data, size_t len) = 0;
    };
    // 落地方向:标准输出
    class StdoutSink : public LogSink
    {
    public:
        // 将日志消息落地标准输出,也就是控制台
        void log(const char *data, size_t len)
        {
            std::cout.write(data, len);
        }
    };
    // 落地方向:指定文件
    class FileSink : public LogSink
    {
    public:
        // 该构造函数同时完成打开文件,将文件操作句柄管理起来(通过ostream流管理)
        FileSink(const std::string &pathname) : _pathname(pathname)
        {
            // 1. 创建日志文件所在目录
            util::File::createDirectory(util::File::path(pathname));
            // 2. 创建并打开日志文件
            _ofs.open(_pathname, std::ios::app | std::ios::binary); // 以二进制和追加方式打开文件
            assert(_ofs.is_open());
        }
        // 将日志消息写入标准输出
        void log(const char *data, size_t len)
        {
            _ofs.write(data, len); // 这布就是将日志消息写入指定文件
            assert(_ofs.good());
        }

    private:
        std::string _pathname;
        std::ofstream _ofs;
    };
    // 落地方向:滚动文件(以文件大小进行滚动)
    class RollBySizeSink : public LogSink
    {
    public:
        RollBySizeSink(const std::string &basename, size_t max_size) : _basename(basename), _max_size(max_size), _cur_size(0), _name_count(0)
        {
            std::string pathname = createNewFile(); // 创建文件
            //  1. 创建日志文件所在目录
            util::File::createDirectory(util::File::path(pathname));
            // 2. 创建并打开日志文件
            _ofs.open(pathname, std::ios::app | std::ios::binary); // 以二进制和追加方式打开文件
            assert(_ofs.is_open());
        }
        // 将日志消息写入到标准输出--写入前判断大小，超过最大大小需要切换文件
        void log(const char *data, size_t len)
        {
            if (_cur_size >= _max_size)
            {
                _ofs.close(); // 关闭被该ofstream已经管理起来的文件,否则资源泄露
                std::string pathname = createNewFile();
                _ofs.open(pathname, std::ios::app | std::ios::binary);
                assert(_ofs.is_open());
                _cur_size = 0; //
            }

            _ofs.write(data, len); // 向指定文件写入数据
            assert(_ofs.good());
            _cur_size += len;
        }

    private:
        // 进行大小判断，超过指定大小则创建新文件
        std::string createNewFile()
        {
            // 获取系统时间,以时间+之前的根目录一起来构造文件扩展名
            time_t t = util::Date::getTime();
            struct tm lt;
            localtime_r(&t, &lt); // 将时间戳转换成年月日时分秒格式
            std::stringstream filename;
            filename << _basename;
            filename << lt.tm_year + 1900;
            filename << lt.tm_mon + 1;
            filename << lt.tm_mday;
            filename << lt.tm_hour;
            filename << lt.tm_min;
            filename << lt.tm_sec;
            filename << "-";
            filename << _name_count++;
            filename << ".log";
            return filename.str();
        }

    private:
        // 通过基础文件名 + 扩展文件名(以时间生成) -> 组成一个实际当前输出的文件名
        std::string _basename; // 基础文件名 ./logs/base -> ./logs/base/2223344.txt
        std::ofstream _ofs;    // 管理打开文件的流
        size_t _max_size;      // 记录最大大小，当前文件超过这个大小需切换文件
        size_t _cur_size;      // 记录当前文件已经的数据大小
        size_t _name_count;
    };

    // 使用模版 + 完美转发(解决不同工厂的构造函数的难题)实例化出不同的工厂类
    class SinkFactor
    {
    public:
        template <typename sinktype, typename... Args>
        static LogSink::ptr create(Args &&...args)
        {
            return std::make_shared<sinktype>(std::forward<Args>(args)...); // 将参数包展开再进行传递
        }
    };
}

#endif